// this will be the first part of the middleware that checkout the
// headers and request and extract the parts that we need for the operation
const {
  inArray,
  getDebug,
  isJsonqlRequest,
  isContractJson,
  isObject,
  ctxErrorHandler,
  processJwtKeys
} = require('./lib')
const {
  AUTH_TYPE,
  QUERY_NAME,
  MUTATION_NAME,
  CONTRACT_NAME,
  CONTRACT_REQUEST_METHODS,
  API_REQUEST_METHODS,
  PAYLOAD_PARAM_NAME,
  CONDITION_PARAM_NAME,
  RESOLVER_PARAM_NAME,
  JSONP_CALLBACK_NAME
} = require('jsonql-constants')
const { Jsonql406Error } = require('jsonql-errors')
const processContract = require('./lib/contract-generator/process-contract')
const { getQueryFromPayload, getMutationFromPayload } = require('jsonql-utils')

const debug = getDebug('init-middleware')

/**
 * Just figure out what is the calling methods here
 * @param {object} ctx koa context
 * @return {*} false on unknown
 */
const getBaseResolverType = function(ctx) {
  const [ POST, PUT ] = API_REQUEST_METHODS;
  const { method } = ctx;
  switch (true) {
    case inArray(CONTRACT_REQUEST_METHODS, method):
      return CONTRACT_NAME;
    case method === POST:
      return QUERY_NAME;
    case method === PUT:
      return MUTATION_NAME;
  }
}

/**
 * check if it is auth type
 * @param {string} type we need this to be the QUERY_TYPE
 * @param {string} name from ctx.request.body to find the issuer name present or not
 * @param {object} opts config we need that to find the custom names
 */
const isAuthType = function(type, name, opts) {
  const { logoutHandlerName, loginHandlerName } = opts;
  const AUTH_TYPE_METHODS = [loginHandlerName, logoutHandlerName]
  if (type === QUERY_NAME) {
    return inArray(AUTH_TYPE_METHODS, name) ? AUTH_TYPE : type;
  }
  return type;
}

/**
 * new in v1.3.0 jsonp handler
 * @param {object} ctx koa context
 * @return {boolean|string} return resolverName or false when its not
 */
const isJsonpCall = function(ctx) {
  if (ctx.query && ctx.query[JSONP_CALLBACK_NAME]) {
    return ctx.query[JSONP_CALLBACK_NAME]
  }
  return false;
}

/**
 * v1.2.0 add setter and getter and store in the ctx for use later
 * @param {object} opts configuration
 * @param {function} setter nodeCache set
 * @param {function} getter nodeCache get
 * @return {function} middleware
 */
module.exports = function(opts) {
  // export
  return async function(ctx, next) {
    ctx.state.jsonql = {};
    // ctx.state.jsonql.setter = setter;
    // ctx.state.jsonql.getter = getter;
    // first check if its calling the jsonql api
    const isJsonql = isJsonqlRequest(ctx, opts)
    // init our own context
    ctx.state.jsonql.isReq = isJsonql;
    if (isJsonql) {
      // its only call once
      ctx.state.jsonql.contract = await processContract(ctx, opts)
      // v1.2.0 grabbing the public / private keys
      opts = await processJwtKeys(ctx, opts)
      // debug('processJwtKeys', opts)
      // get what is calling
      let payload = ctx.request.body;
      // new in v1.3.0 - test for jsonp type
      if (opts.enableJsonp === true) {
        const jsonp = isJsonpCall(ctx)
        if (jsonp !== false) {
          ctx.state.jsonql.jsonp = jsonp;
          // mutate the payload to let the rest to work
          payload = {[jsonp]: payload}
          debug('jsonp', jsonp, payload)
        }
      }
      // start
      let type = getBaseResolverType(ctx)
      if (type) {
        let params;
        switch(type) {
          case CONTRACT_NAME:
            ctx.state.jsonql.resolverType = CONTRACT_NAME;
          break;
          case QUERY_NAME:
            params = getQueryFromPayload(payload)
          break;
          case MUTATION_NAME:
            params = getMutationFromPayload(payload)
          break;
          default: // should never happen!
            throw new JsonqlError(`[init-middleware] ${type} is unknown!`)
        }
        if (type !== CONTRACT_NAME) {
          let name = params[RESOLVER_PARAM_NAME];
          ctx.state.jsonql.resolverName = name;
          ctx.state.jsonql.payload = params;
          ctx.state.jsonql.resolverType = isAuthType(type, name, opts)
        }
      } else {
        return ctxErrorHandler(ctx, 406, {
            message: 'Payload is not the expected object type',
            payload
          }
        );
      }
    }
    await next()
  }
}
